home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Cream of the Crop 1
/
Cream of the Crop 1.iso
/
PROGRAM
/
DDJ0192.ARJ
/
GRPHPROG.ASC
< prev
next >
Wrap
Text File
|
1991-11-21
|
20KB
|
471 lines
_GRAPHIC PROGRAMMING COLUMN_
by Michael Abrash
[LISTING ONE]
; Draws all pixels in the list of horizontal lines passed in, in
; mode X, the VGA's undocumented 320x240 256-color mode. Clips to
; the rectangle specified by (ClipMinX,ClipMinY),(ClipMaxX,ClipMaxY).
; Draws to the page specified by CurrentPageBase.
; C near-callable as:
; void DrawHorizontalLineList(struct HLineList * HLineListPtr,
; int Color);
;
; All assembly code tested with TASM 2.0 and MASM 5.0
SCREEN_WIDTH equ 320
SCREEN_SEGMENT equ 0a000h
SC_INDEX equ 03c4h ;Sequence Controller Index
MAP_MASK equ 2 ;Map Mask register index in SC
HLine struc
XStart dw ? ;X coordinate of leftmost pixel in line
XEnd dw ? ;X coordinate of rightmost pixel in line
HLine ends
HLineList struc
Lngth dw ? ;# of horizontal lines
YStart dw ? ;Y coordinate of topmost line
HLinePtr dw ? ;pointer to list of horz lines
HLineList ends
Parms struc
dw 2 dup(?) ;return address & pushed BP
HLineListPtr dw ? ;pointer to HLineList structure
Color dw ? ;color with which to fill
Parms ends
.model small
.data
extrn _CurrentPageBase:word,_ClipMinX:word
extrn _ClipMinY:word,_ClipMaxX:word,_ClipMaxY:word
; Plane masks for clipping left and right edges of rectangle.
LeftClipPlaneMask db 00fh,00eh,00ch,008h
RightClipPlaneMask db 001h,003h,007h,00fh
.code
align 2
ToFillDone:
jmp FillDone
public _DrawHorizontalLineList
align 2
_DrawHorizontalLineList proc
push bp ;preserve caller's stack frame
mov bp,sp ;point to our stack frame
push si ;preserve caller's register variables
push di
cld ;make string instructions inc pointers
mov dx,SC_INDEX
mov al,MAP_MASK
out dx,al ;point SC Index to the Map Mask
mov ax,SCREEN_SEGMENT
mov es,ax ;point ES to display memory for REP STOS
mov si,[bp+HLineListPtr] ;point to the line list
mov bx,[si+HLinePtr] ;point to the XStart/XEnd descriptor
; for the first (top) horizontal line
mov cx,[si+YStart] ;first scan line to draw
mov si,[si+Lngth] ;# of scan lines to draw
cmp si,0 ;are there any lines to draw?
jle ToFillDone ;no, so we're done
cmp cx,[_ClipMinY] ;clipped at top?
jge MinYNotClipped ;no
neg cx ;yes, discard however many lines are
add cx,[_ClipMinY] ; clipped
sub si,cx ;that many fewer lines to draw
jle ToFillDone ;no lines left to draw
shl cx,1 ;lines to skip*2
shl cx,1 ;lines to skip*4
add bx,cx ;advance through the line list
mov cx,[_ClipMinY] ;start at the top clip line
MinYNotClipped:
mov dx,si
add dx,cx ;bottom row to draw + 1
cmp dx,[_ClipMaxY] ;clipped at bottom?
jle MaxYNotClipped ;no
sub dx,[_ClipMaxY] ;# of lines to clip off the bottom
sub si,dx ;# of lines left to draw
jle ToFillDone ;all lines are clipped
MaxYNotClipped:
mov ax,SCREEN_WIDTH/4 ;point to the start of the first
mul cx ; scan line on which to draw
add ax,[_CurrentPageBase] ;offset of first line
mov dx,ax ;ES:DX points to first scan line to
; draw
mov ah,byte ptr [bp+Color] ;color with which to fill
FillLoop:
push bx ;remember line list location
push dx ;remember offset of start of line
push si ;remember # of lines to draw
mov di,[bx+XStart] ;left edge of fill on this line
cmp di,[_ClipMinX] ;clipped to left edge?
jge MinXNotClipped ;no
mov di,[_ClipMinX] ;yes, clip to the left edge
MinXNotClipped:
mov si,di
mov cx,[bx+XEnd] ;right edge of fill
cmp cx,[_ClipMaxX] ;clipped to right edge?
jl MaxXNotClipped ;no
mov cx,[_ClipMaxX] ;yes, clip to the right edge
dec cx
MaxXNotClipped:
cmp cx,di
jl LineFillDone ;skip if negative width
shr di,1 ;X/4 = offset of first rect pixel in scan
shr di,1 ; line
add di,dx ;offset of first rect pixel in display mem
mov dx,si ;XStart
and si,0003h ;look up left edge plane mask
mov bh,LeftClipPlaneMask[si] ; to clip & put in BH
mov si,cx
and si,0003h ;look up right edge plane
mov bl,RightClipPlaneMask[si] ; mask to clip & put in BL
and dx,not 011b ;calculate # of addresses across rect
sub cx,dx
shr cx,1
shr cx,1 ;# of addresses across rectangle to fill - 1
jnz MasksSet ;there's more than one byte to draw
and bh,bl ;there's only one byte, so combine the left
; and right edge clip masks
MasksSet:
mov dx,SC_INDEX+1 ;already points to the Map Mask reg
FillRowsLoop:
mov al,bh ;put left-edge clip mask in AL
out dx,al ;set the left-edge plane (clip) mask
mov al,ah ;put color in AL
stosb ;draw the left edge
dec cx ;count off left edge byte
js FillLoopBottom ;that's the only byte
jz DoRightEdge ;there are only two bytes
mov al,00fh ;middle addresses are drawn 4 pixels at a pop
out dx,al ;set the middle pixel mask to no clip
mov al,ah ;put color in AL
rep stosb ;draw the middle addresses four pixels apiece
DoRightEdge:
mov al,bl ;put right-edge clip mask in AL
out dx,al ;set the right-edge plane (clip) mask
mov al,ah ;put color in AL
stosb ;draw the right edge
FillLoopBottom:
LineFillDone:
pop si ;retrieve # of lines to draw
pop dx ;retrieve offset of start of line
pop bx ;retrieve line list location
add dx,SCREEN_WIDTH/4 ;point to start of next line
add bx,size HLine ;point to the next line descriptor
dec si ;count down lines
jnz FillLoop
FillDone:
pop di ;restore caller's register variables
pop si
pop bp ;restore caller's stack frame
ret
_DrawHorizontalLineList endp
end
[LISTING TWO]
/* Matrix arithmetic functions.
Tested with Borland C++ 2.0 in the small model */
/* Matrix multiplies Xform by SourceVec, and stores the result in
DestVec. Multiplies a 4x4 matrix times a 4x1 matrix; the result
is a 4x1 matrix, as follows:
-- -- -- -- -- --
| | | 4 | | 4 |
| 4x4 | X | x | = | x |
| | | 1 | | 1 |
-- -- -- -- -- -- */
void XformVec(double Xform[4][4], double * SourceVec,
double * DestVec)
{
int i,j;
for (i=0; i<4; i++) {
DestVec[i] = 0;
for (j=0; j<4; j++)
DestVec[i] += Xform[i][j] * SourceVec[j];
}
}
/* Matrix multiplies SourceXform1 by SourceXform2 and stores the
result in DestXform. Multiplies a 4x4 matrix times a 4x4 matrix;
the result is a 4x4 matrix, as follows:
-- -- -- -- -- --
| | | | | |
| 4x4 | X | 4x4 | = | 4x4 |
| | | | | |
-- -- -- -- -- -- */
void ConcatXforms(double SourceXform1[4][4], double SourceXform2[4][4],
double DestXform[4][4])
{
int i,j,k;
for (i=0; i<4; i++) {
for (j=0; j<4; j++) {
DestXform[i][j] = 0;
for (k=0; k<4; k++)
DestXform[i][j] += SourceXform1[i][k] * SourceXform2[k][j];
}
}
}
[LISTING THREE]
/* Transforms convex polygon Poly (which has PolyLength vertices),
performing the transformation according to Xform (which generally
represents a transformation from object space through world space
to view space), then projects the transformed polygon onto the
screen and draws it in color Color. Also updates the extent of the
rectangle (EraseRect) that's used to erase the screen later.
Tested with Borland C++ 2.0 in the small model */
#include "polygon.h"
void XformAndProjectPoly(double Xform[4][4], struct Point3 * Poly,
int PolyLength, int Color)
{
int i;
struct Point3 XformedPoly[MAX_POLY_LENGTH];
struct Point ProjectedPoly[MAX_POLY_LENGTH];
struct PointListHeader Polygon;
/* Transform to view space, then project to the screen */
for (i=0; i<PolyLength; i++) {
/* Transform to view space */
XformVec(Xform, (double *)&Poly[i], (double *)&XformedPoly[i]);
/* Project the X & Y coordinates to the screen, rounding to the
nearest integral coordinates. The Y coordinate is negated to
flip from view space, where increasing Y is up, to screen
space, where increasing Y is down. Add in half the screen
width and height to center on the screen */
ProjectedPoly[i].X = ((int) (XformedPoly[i].X/XformedPoly[i].Z *
PROJECTION_RATIO*(SCREEN_WIDTH/2.0)+0.5))+SCREEN_WIDTH/2;
ProjectedPoly[i].Y = ((int) (XformedPoly[i].Y/XformedPoly[i].Z *
-1.0 * PROJECTION_RATIO * (SCREEN_WIDTH / 2.0) + 0.5)) +
SCREEN_HEIGHT/2;
/* Appropriately adjust the extent of the rectangle used to
erase this page later */
if (ProjectedPoly[i].X > EraseRect[NonDisplayedPage].Right)
if (ProjectedPoly[i].X < SCREEN_WIDTH)
EraseRect[NonDisplayedPage].Right = ProjectedPoly[i].X;
else EraseRect[NonDisplayedPage].Right = SCREEN_WIDTH;
if (ProjectedPoly[i].Y > EraseRect[NonDisplayedPage].Bottom)
if (ProjectedPoly[i].Y < SCREEN_HEIGHT)
EraseRect[NonDisplayedPage].Bottom = ProjectedPoly[i].Y;
else EraseRect[NonDisplayedPage].Bottom = SCREEN_HEIGHT;
if (ProjectedPoly[i].X < EraseRect[NonDisplayedPage].Left)
if (ProjectedPoly[i].X > 0)
EraseRect[NonDisplayedPage].Left = ProjectedPoly[i].X;
else EraseRect[NonDisplayedPage].Left = 0;
if (ProjectedPoly[i].Y < EraseRect[NonDisplayedPage].Top)
if (ProjectedPoly[i].Y > 0)
EraseRect[NonDisplayedPage].Top = ProjectedPoly[i].Y;
else EraseRect[NonDisplayedPage].Top = 0;
}
/* Draw the polygon */
DRAW_POLYGON(ProjectedPoly, PolyLength, Color, 0, 0);
}
[LISTING FOUR]
/* POLYGON.H: Header file for polygon-filling code, also includes
a number of useful items for 3D animation. */
#define MAX_POLY_LENGTH 4 /* four vertices is the max per poly */
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define PAGE0_START_OFFSET 0
#define PAGE1_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH)/4)
/* Ratio: distance from viewpoint to projection plane / width of
projection plane. Defines the width of the field of view. Lower
absolute values = wider fields of view; higher values = narrower */
#define PROJECTION_RATIO -2.0 /* negative because visible Z
coordinates are negative */
/* Draws the polygon described by the point list PointList in color
Color with all vertices offset by (X,Y) */
#define DRAW_POLYGON(PointList,NumPoints,Color,X,Y) \
Polygon.Length = NumPoints; \
Polygon.PointPtr = PointList; \
FillConvexPolygon(&Polygon, Color, X, Y);
/* Describes a single 2D point */
struct Point {
int X; /* X coordinate */
int Y; /* Y coordinate */
};
/* Describes a single 3D point in homogeneous coordinates */
struct Point3 {
double X; /* X coordinate */
double Y; /* Y coordinate */
double Z; /* Z coordinate */
double W;
};
/* Describes a series of points (used to store a list of vertices that
describe a polygon; each vertex is assumed to connect to the two
adjacent vertices, and the last vertex is assumed to connect to the
first) */
struct PointListHeader {
int Length; /* # of points */
struct Point * PointPtr; /* pointer to list of points */
};
/* Describes the beginning and ending X coordinates of a single
horizontal line */
struct HLine {
int XStart; /* X coordinate of leftmost pixel in line */
int XEnd; /* X coordinate of rightmost pixel in line */
};
/* Describes a Length-long series of horizontal lines, all assumed to
be on contiguous scan lines starting at YStart and proceeding
downward (used to describe a scan-converted polygon to the
low-level hardware-dependent drawing code) */
struct HLineList {
int Length; /* # of horizontal lines */
int YStart; /* Y coordinate of topmost line */
struct HLine * HLinePtr; /* pointer to list of horz lines */
};
struct Rect { int Left, Top, Right, Bottom; };
extern void XformVec(double Xform[4][4], double * SourceVec,
double * DestVec);
extern void ConcatXforms(double SourceXform1[4][4],
double SourceXform2[4][4], double DestXform[4][4]);
extern void XformAndProjectPoly(double Xform[4][4],
struct Point3 * Poly, int PolyLength, int Color);
extern int FillConvexPolygon(struct PointListHeader *, int, int, int);
extern void Set320x240Mode(void);
extern void ShowPage(unsigned int StartOffset);
extern void FillRectangleX(int StartX, int StartY, int EndX,
int EndY, unsigned int PageBase, int Color);
extern int DisplayedPage, NonDisplayedPage;
extern struct Rect EraseRect[];
[LISTING FIVE]
/* Simple 3D drawing program to view a polygon as it rotates in
mode X. View space is congruent with world space, with the
viewpoint fixed at the origin (0,0,0) of world space, looking in
the direction of increasingly negative Z. A right-handed
coordinate system is used throughout.
Tested with Borland C++ 2.0 in the small model */
#include <conio.h>
#include <stdio.h>
#include <dos.h>
#include <math.h>
#include "polygon.h"
void main(void);
/* Base offset of page to which to draw */
unsigned int CurrentPageBase = 0;
/* Clip rectangle; clips to the screen */
int ClipMinX=0, ClipMinY=0;
int ClipMaxX=SCREEN_WIDTH, ClipMaxY=SCREEN_HEIGHT;
/* Rectangle specifying extent to be erased in each page */
struct Rect EraseRect[2] = { {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT},
{0, 0, SCREEN_WIDTH, SCREEN_HEIGHT} };
/* Transformation from polygon's object space to world space.
Initially set up to perform no rotation and to move the polygon
into world space -140 units away from the origin down the Z axis.
Given the viewing point, -140 down the Z axis means 140 units away
straight ahead in the direction of view. The program dynamically
changes the rotation and translation */
static double PolyWorldXform[4][4] = {
{1.0, 0.0, 0.0, 0.0},
{0.0, 1.0, 0.0, 0.0},
{0.0, 0.0, 1.0, -140.0},
{0.0, 0.0, 0.0, 1.0} };
/* Transformation from world space into view space. In this program,
the view point is fixed at the origin of world space, looking down
the Z axis in the direction of increasingly negative Z, so view
space is identical to world space; this is the identity matrix */
static double WorldViewXform[4][4] = {
{1.0, 0.0, 0.0, 0.0},
{0.0, 1.0, 0.0, 0.0},
{0.0, 0.0, 1.0, 0.0},
{0.0, 0.0, 0.0, 1.0}
};
static unsigned int PageStartOffsets[2] =
{PAGE0_START_OFFSET,PAGE1_START_OFFSET};
int DisplayedPage, NonDisplayedPage;
void main() {
int Done = 0;
double WorkingXform[4][4];
static struct Point3 TestPoly[] =
{{-30,-15,0,1},{0,15,0,1},{10,-5,0,1}};
#define TEST_POLY_LENGTH (sizeof(TestPoly)/sizeof(struct Point3))
double Rotation = M_PI / 60.0; /* initial rotation = 3 degrees */
union REGS regset;
Set320x240Mode();
ShowPage(PageStartOffsets[DisplayedPage = 0]);
/* Keep rotating the polygon, drawing it to the undisplayed page,
and flipping the page to show it */
do {
CurrentPageBase = /* select other page for drawing to */
PageStartOffsets[NonDisplayedPage = DisplayedPage ^ 1];
/* Modify the object space to world space transformation matrix
for the current rotation around the Y axis */
PolyWorldXform[0][0] = PolyWorldXform[2][2] = cos(Rotation);
PolyWorldXform[2][0] = -(PolyWorldXform[0][2] = sin(Rotation));
/* Concatenate the object-to-world and world-to-view
transformations to make a transformation matrix that will
convert vertices from object space to view space in a single
operation */
ConcatXforms(WorldViewXform, PolyWorldXform, WorkingXform);
/* Clear the portion of the non-displayed page that was drawn
to last time, then reset the erase extent */
FillRectangleX(EraseRect[NonDisplayedPage].Left,
EraseRect[NonDisplayedPage].Top,
EraseRect[NonDisplayedPage].Right,
EraseRect[NonDisplayedPage].Bottom, CurrentPageBase, 0);
EraseRect[NonDisplayedPage].Left =
EraseRect[NonDisplayedPage].Top = 0x7FFF;
EraseRect[NonDisplayedPage].Right =
EraseRect[NonDisplayedPage].Bottom = 0;
/* Transform the polygon, project it on the screen, draw it */
XformAndProjectPoly(WorkingXform, TestPoly, TEST_POLY_LENGTH,9);
/* Flip to display the page into which we just drew */
ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]);
/* Rotate 6 degrees farther around the Y axis */
if ((Rotation += (M_PI/30.0)) >= (M_PI*2)) Rotation -= M_PI*2;
if (kbhit()) {
switch (getch()) {
case 0x1B: /* Esc to exit */
Done = 1; break;
case 'A': case 'a': /* away (-Z) */
PolyWorldXform[2][3] -= 3.0; break;
case 'T': /* towards (+Z). Don't allow to get too */
case 't': /* close, so Z clipping isn't needed */
if (PolyWorldXform[2][3] < -40.0)
PolyWorldXform[2][3] += 3.0; break;
case 0: /* extended code */
switch (getch()) {
case 0x4B: /* left (-X) */
PolyWorldXform[0][3] -= 3.0; break;
case 0x4D: /* right (+X) */
PolyWorldXform[0][3] += 3.0; break;
case 0x48: /* up (+Y) */
PolyWorldXform[1][3] += 3.0; break;
case 0x50: /* down (-Y) */
PolyWorldXform[1][3] -= 3.0; break;
default:
break;
}
break;
default: /* any other key to pause */
getch(); break;
}
}
} while (!Done);
/* Return to text mode and exit */
regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */
int86(0x10, ®set, ®set);
}